﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace WindowsFormsAppPfs
{
    public class RsaCryptService
    {
        private Encoding _encoding = Encoding.UTF8;

        public Encoding Encoding
        {
            get { return _encoding; }
            set { _encoding = value; }
        }

        private X509ContentType _x509ContentType = X509ContentType.Cert;

        public X509ContentType X509ContentType
        {
            get { return _x509ContentType; }
            set { _x509ContentType = value; }
        }

        private StoreName _storeName = StoreName.My;

        public StoreName StoreName
        {
            get { return _storeName; }
            set { _storeName = value; }
        }

        private StoreLocation _locationr = StoreLocation.CurrentUser;

        public StoreLocation Location
        {
            get { return _locationr; }
            set { _locationr = value; }
        }

        private X509Certificate2 _certificate;

        public X509Certificate2 Certificate
        {
            get { return _certificate; }
            private set { _certificate = value; }
        }

        public X509Certificate2 CreateCertificate(string CertFile, string Password)
        {
            if (CertFile == null) throw new ArgumentNullException("CertFile");
            if (Password == null) throw new ArgumentNullException("Password");
            if (string.IsNullOrEmpty(Password))
            {
                this.Certificate = new X509Certificate2(CertFile);
            }
            else
            {
                this.Certificate = new X509Certificate2(CertFile, Password);
            }

            return this.Certificate;
        }

        public X509Certificate2 ImportCertificate(string CertFile, string Password)
        {
            if (CertFile == null) throw new ArgumentNullException("CertFile");
            if (Password == null) throw new ArgumentNullException("Password");
            var cert = this.CreateCertificate(CertFile, Password);
            if (cert == null) return null;

            X509Store store = new X509Store(this.StoreName, this.Location);
            store.Open(OpenFlags.ReadWrite);
            store.Add(cert);
            store.Close();
            return cert;
        }

        public bool ExportCertificate(string CertSubjectName, string ExportFile)
        {
            if (CertSubjectName == null) throw new ArgumentNullException("CertSubjectName");
            if (ExportFile == null) throw new ArgumentNullException("ExportFile");
            return ExportCertificate(CertSubjectName, null, ExportFile);
        }

        public bool ExportCertificate(string CertSubjectName, string Password, string ExportFile)
        {
            if (CertSubjectName == null) throw new ArgumentNullException("CertSubjectName");
            if (ExportFile == null) throw new ArgumentNullException("ExportFile");
            X509Store store = new X509Store(this.StoreName, this.Location);
            store.Open(OpenFlags.ReadOnly);
            FileStream fileStream = null;
            try
            {
                fileStream = new FileStream(ExportFile, FileMode.Create, FileAccess.Write);
                foreach (X509Certificate2 cert in store.Certificates)
                {
                    if (cert.Subject == CertSubjectName)
                    {
                        byte[] CertByte;

                        if (string.IsNullOrEmpty(Password))
                        {
                            CertByte = cert.Export(X509ContentType);
                        }
                        else
                        {
                            CertByte = cert.Export(X509ContentType, Password);
                        }
                        fileStream.Write(CertByte, 0, CertByte.Length);
                        return true;
                    }
                }
            }
            finally
            {
                if (fileStream != null) fileStream.Dispose();
                store.Close();
            }
            return false;
        }

        public void VerifyCertificate(X509Certificate2 Cert)
        {
            if (Cert == null) throw new ArgumentNullException("Ccert");
            X509Chain chain = new X509Chain();

            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
            //RevocationStatusUnknown
            chain.Build(Cert);

            if (chain.ChainStatus.Length > 0)
            {
                StringBuilder builder = new StringBuilder();
                builder.AppendLine("憑證檢查錯誤：/r/n");
                foreach (X509ChainStatus status in chain.ChainStatus)
                {
                    builder.Append(string.Format("{0}={1}/r/n", status.Status, status.StatusInformation));
                }

                throw new ApplicationException(builder.ToString());
            }
            if (Cert.NotAfter <= DateTime.Now)
            {
                throw new ApplicationException(string.Format("憑証過期"));
            }
        }

        public byte[] Encryptor(byte[] OriginalData)
        {
            if (OriginalData == null) throw new ArgumentNullException("OriginalData");
            if (OriginalData.Length <= 0) throw new ArgumentOutOfRangeException("OriginalData");
            if (this.Certificate == null)
            {
                //使用資源檔裡的預設憑證
                var defaultCert = WindowsFormsAppPfs.Properties.Resources.TestPfx;
                this.Certificate = new X509Certificate2(defaultCert, "123456");
            }
            var rsaCrypto = (RSACryptoServiceProvider)this.Certificate.PublicKey.Key;

            int bufferSize = (rsaCrypto.KeySize / 8) - 11;
            byte[] buffer = new byte[bufferSize];
            //分段加密
            using (MemoryStream input = new MemoryStream(OriginalData))
            using (MemoryStream ouput = new MemoryStream())
            {
                while (true)
                {
                    int readLine = input.Read(buffer, 0, bufferSize);
                    if (readLine <= 0)
                    {
                        break;
                    }
                    byte[] temp = new byte[readLine];
                    Array.Copy(buffer, 0, temp, 0, readLine);
                    byte[] encrypt = rsaCrypto.Encrypt(temp, false);
                    ouput.Write(encrypt, 0, encrypt.Length);
                }
                return ouput.ToArray();
            }
        }

        public string EncryptString(string OriginalString)
        {
            if (OriginalString == null) throw new ArgumentNullException("OriginalString");

            var originalData = this.Encoding.GetBytes(OriginalString);
            var encryptData = this.Encryptor(originalData);
            var base64 = Convert.ToBase64String(encryptData);
            return base64;
        }

        public void EncryptFile(string OriginalFile, string EncrytpFile)
        {
            using (FileStream originalStream = new FileStream(OriginalFile, FileMode.Open, FileAccess.Read))
            using (FileStream encrytpStream = new FileStream(EncrytpFile, FileMode.Create, FileAccess.Write))
            {
                //加密
                var dataByteArray = new byte[originalStream.Length];
                originalStream.Read(dataByteArray, 0, dataByteArray.Length);
                var encryptData = this.Encryptor(dataByteArray);
                //寫檔
                encrytpStream.Write(encryptData, 0, encryptData.Length);
            }
        }

        public byte[] Decryptor(byte[] EncryptDada)
        {
            if (EncryptDada == null) throw new ArgumentNullException("EncryptDada");
            if (EncryptDada.Length <= 0) throw new ArgumentOutOfRangeException("EncryptDada");

            if (this.Certificate == null)
            {
                //使用資源檔裡的預設憑證
                var defaultCert = WindowsFormsAppPfs.Properties.Resources.TestPfx;
                this.Certificate = new X509Certificate2(defaultCert, "123456");
            }

            var rsaCrypto = (RSACryptoServiceProvider)this.Certificate.PrivateKey;

            int keySize = rsaCrypto.KeySize / 8;
            byte[] buffer = new byte[keySize];

            using (MemoryStream input = new MemoryStream(EncryptDada))
            using (MemoryStream output = new MemoryStream())
            {
                while (true)
                {
                    int readLine = input.Read(buffer, 0, keySize);
                    if (readLine <= 0)
                    {
                        break;
                    }
                    byte[] temp = new byte[readLine];
                    Array.Copy(buffer, 0, temp, 0, readLine);
                    byte[] decrypt = rsaCrypto.Decrypt(temp, false);
                    output.Write(decrypt, 0, decrypt.Length);
                }
                return output.ToArray();
            }
        }

        public string DecryptString(string EncryptString)
        {
            if (EncryptString == null) throw new ArgumentNullException("EncryptString");
            var encryptData = Convert.FromBase64String(EncryptString);
            var decryptData = this.Decryptor(encryptData);
            var decryptString = this.Encoding.GetString(decryptData);
            return decryptString;
        }

        public void DecryptFile(string EncrytpFile, string DecrytpFile)
        {
            if (EncrytpFile == null) throw new ArgumentNullException("EncrytpFile");
            if (DecrytpFile == null) throw new ArgumentNullException("DecrytpFile");
            using (FileStream encrytpStream = new FileStream(EncrytpFile, FileMode.Open, FileAccess.Read))
            using (FileStream decrytpStream = new FileStream(DecrytpFile, FileMode.Create, FileAccess.Write))
            {
                //解密
                var dataByteArray = new byte[encrytpStream.Length];
                encrytpStream.Read(dataByteArray, 0, dataByteArray.Length);
                var decryptData = this.Decryptor(dataByteArray);
                //寫檔
                decrytpStream.Write(decryptData, 0, decryptData.Length);
            }
        }
    }
}